
import * as HTTPS from 'https';
import * as HTTP from 'http';
import * as URL from 'url';
import { readFileSync } from 'fs';
import got from 'got/dist/source';

type RequestType = 'xml' | 'json' | 'text' | 'plain' | 'formdata' | undefined
type ResponseType = 'text' | 'json' | 'buffer' | undefined

var timeout_num = 30 * 1000;
var taskGenId = 0;
function makeTaskId() {
    ++taskGenId;
    return taskGenId;
}

interface ifRequestOption {
    /**超时时间单位毫秒 */
    timeout?: number
    request_type?: RequestType,
    respon_type?: ResponseType,
    respon_head?: boolean,
    ca_name?: string
}

interface ifTask {
    taskId: number,
    timeout: number,
    url: string,
    headers?: any,
    type: string,
    data: any,
    retry: number,
    res?: HTTP.IncomingMessage,
    respon_data?: Buffer,
    respon_len?: number,

    option: ifRequestOption,

    resolve: Function,
    reject: Function
}

var task_cache: { [x: string]: ifTask } = {}

let cert_cache: {
    [name: string]: {
        cert: Buffer,
        key: Buffer,
        pass: string
    }
} = {};

export function loadCert(name: string, cert_path: string, key_path: string, passphrase: string) {
    try {
        cert_cache[name] = {
            cert: readFileSync(cert_path),
            key: readFileSync(key_path),
            pass: passphrase
        }
        return true;
    }
    catch (e) {
        return false;
    }
}

function formatURL(url: string) {
    // 这里url增加一个 http https 头检查
    let checkHead = url.slice(0, 7);
    if (checkHead != 'http://' && checkHead != 'https:/') {
        // 自动增加一个头上去
        url = 'http://' + url;
    }
    return url;
}

function formatType(type: string) {
    return type.toLowerCase();
}

function formatOption(_option?: ifRequestOption | string) {
    let option: { [key: string]: string | number } = {};
    if (typeof _option == 'string') {
        option = { dtype: _option };
    } else {
        option = Object.assign(option, _option || {});
    }
    return option;
}

/**
 * 异步流程执行网络请求
 * @param type 
 * @param url 
 * @param data 
 * @param retry 
 * @param headers 
 * @param dtype 
 * @param rtype 
 */
export function deprecated_http_quest<T>(type: 'get' | 'post' | string, url: string, data: any = {}, retry: number = 0, headers: any = {}, _option?: ifRequestOption | string): Promise<T> {

    url = formatURL(url);
    type = formatType(type);

    return new Promise(function (resolve, reject) {
        let taskId = makeTaskId();
        let timeOut = timeout_num;
        let option = formatOption(_option);
        if (option.timeout) {
            timeOut = option.timeout as number;
        }

        task_cache[taskId] = {
            taskId: taskId,
            timeout: timeOut,
            url: url,
            retry: retry,
            data: data,
            type: type,
            headers: headers,
            option: option,
            resolve: resolve,
            reject: reject
        };

        make_quest(taskId);
    });
}

export async function http_quest<T>(type: 'get' | 'post' | string, url: string, data: any = {}, retry?: number, headers?: any, _option?: ifRequestOption | string): Promise<T> {

    url = formatURL(url);
    type = formatType(type);
    let option = formatOption(_option);

    let gotOption: { [k: string]: any } = {
        headers: headers || {},
        responseType: option.respon_type,
        retry: retry || 0,
        timeout: option.timeout || timeout_num,
    }

    let resp;
    if (type === "get") {
        resp = await got<T>(joinUrl(url, data), gotOption);
    } else {

        let type = option.request_type as string || 'text';
        let { contents, headers } = getHeadersByType(type, data);
        gotOption.body = contents;
        gotOption.headers = Object.assign(gotOption.headers, headers);

        resp = await got.post<T>(url, gotOption);
    }

    return resp.body;
}

function onResponse(taskid: number, res?: HTTP.IncomingMessage) {
    let task = task_cache[taskid];
    if (task) {
        task.res = res;
        if (res) {
            let size = Math.min(parseInt(res.headers["content-length"] || '1024'), 128 * 1024);
            task.respon_data = Buffer.alloc(size);
            task.respon_len = 0;

            res.on("data", onData.bind(null, taskid));
            res.on("end", onEnd.bind(null, taskid));
            res.on("error", onEnd.bind(null, taskid));
        }
        else if (!task.respon_data) {
            // 表示开始接收数据了，就不需要超时标志了
            if (task.retry > 0) {
                --task.retry;
            }
            else {
                onEnd(taskid);
            }
        }
    }
}

function onData(taskid: number, data: Buffer) {
    let info = task_cache[taskid];
    if (!info || info.respon_len == undefined || info.respon_data == undefined) return;
    if (info.respon_len + data.length > info.respon_data.length) {
        // 数据不够放了，一般不存在,
        let nbuff = Buffer.alloc(info.respon_len + data.length + 2 * 1024); // 额外多2K的数据容量
        info.respon_data.copy(nbuff, 0, 0, info.respon_len);
        info.respon_data = nbuff;
    }

    info.respon_len += data.copy(info.respon_data, info.respon_len, 0, data.length);
}

function onEnd(taskid: number) {
    let info = task_cache[taskid];
    if (!info) return;
    let out = null;
    if (info.respon_data && info.respon_len) {
        out = info.respon_data.slice(0, info.respon_len);
    }

    switch (info.option.respon_type) {
        case 'json':
            try {
                out && (out = JSON.parse(out.toString()));
            }
            catch (e) {
                out && (out = out.toString())
            }
            break;
        case 'buffer':
            break;
        case 'text':
        case undefined:
            out && (out = out.toString())
            break;
        default:
            break;

    }

    if (out) {
        if (info.option.respon_head) {
            info.resolve([out, info.res ? info.res.headers : {}])
        }
        else {
            info.resolve(out)
        }
    }
    else {
        if (info.res) {
            if (info.res.statusCode == 200) {
                if (info.option.respon_head) {
                    info.resolve([out, info.res ? info.res.headers : {}])
                }
                else {
                    info.resolve(out)
                }
            }
            else {
                if (info.option.respon_head) {
                    info.reject(['404 ' + info.url, info.res.headers])
                }
                else {
                    info.reject('404 ' + info.url)
                }
            }
        }
        else {
            if (info.option.respon_head) {
                info.reject(['404 ' + info.url, {}])
            }
            else {
                info.reject('404 ' + info.url)
            }
        }

    }
    delete task_cache[taskid];
}

function make_quest(taskId: number) {
    let task = task_cache[taskId]
    if (task.type == 'get') {
        http_get(task);
    }
    else if (task.type == 'post') {
        http_post(task)
    }
    else {
        process.nextTick(function () { onResponse(taskId) });
    }
}

function stringify(method: string, data: any) {
    if (typeof data == 'object') {
        let urldata = [];
        for (let key in data) {
            let value = data[key];
            if (typeof value == 'object') {
                value = JSON.stringify(value);
            }

            urldata.push(`${key}=${encodeURIComponent(value)}`);
        }
        return urldata.join('&')
    }

    if (data == undefined) data = '';
    return encodeURIComponent(data);
}

function joinUrl(url: string, data: any) {
    let urldata = stringify('get', data);
    if (urldata.length > 0) {
        if (url.indexOf('?') >= 0) {
            url = url + '&' + urldata;
        }
        else {
            url = url + '?' + urldata;
        }
    }
    return url;
}

function http_get(task: ifTask) {
    let url = task.url;
    let headers = task.headers;
    try {
        url = joinUrl(url, task.data);
        let opt: HTTPS.RequestOptions = URL.parse(url) as any;
        if (headers) {
            if (!opt['headers']) opt['headers'] = {};
            for (let key in headers) {
                opt['headers'][key] = headers[key];
            }
        }

        let cert_info = cert_cache[task.option.ca_name || ''];
        if (cert_info) {
            opt.cert = cert_info.cert;
            opt.key = cert_info.key;
            opt.passphrase = cert_info.pass;
        }

        let r = (url.indexOf('https://') == 0) ? HTTPS.get(opt, function (res) { onResponse(task.taskId, res); }) : HTTP.get(opt, function (res) { onResponse(task.taskId, res); });
        r.on("error", function (e: Error) { onResponse(task.taskId); })
        r.setTimeout(task.timeout, function () { onResponse(task.taskId); })
    }
    catch (e) {
        process.nextTick(function () { onResponse(task.taskId); });
    }
}

function getHeadersByType(type: string, data: any) {

    let contents = "";
    let headers;

    switch (type) {
        case 'plain': {
            contents = data;
            headers = {
                'Content-Type': 'text/plain',
                'Content-Length': Buffer.byteLength(contents)
            }
            break;
        }
        case 'json': {
            if (typeof data == 'string') {
                contents = data;
            }
            else {
                contents = JSON.stringify(data);
            }
            headers = {
                'Content-Type': 'application/json',
                'Content-Length': Buffer.byteLength(contents)
            }
            break;
        }
        case 'xml': {
            contents = data;
            headers = {
                'Content-Type': 'text/xml',
                // 'Content-Length': contents.length
            }
            break;
        }
        default: {
            contents = stringify('post', data);
            headers = {
                'Content-Type': 'application/x-www-form-urlencoded',
                'Content-Length': contents.length
            }
            break;
        }
    }

    return {
        contents,
        headers,
    }
}

function http_post(task: ifTask) {
    let url = task.url;
    let data = task.data;
    let selfHeaders = task.headers;
    let type = task.option.request_type || 'text';
    try {
        let Query: HTTPS.RequestOptions = URL.parse(url) as any;
        Query['method'] = 'POST';

        let { contents, headers } = getHeadersByType(type, data);
        Query['headers'] = headers;

        if (selfHeaders) {
            for (let key in selfHeaders) {
                Query['headers'][key] = selfHeaders[key];
            }
        }

        let cert_info = cert_cache[task.option.ca_name || ''];
        if (cert_info) {
            Query.cert = cert_info.cert;
            Query.key = cert_info.key;
            Query.passphrase = cert_info.pass;
        }

        let r;
        if (url.indexOf('https://') == 0) {
            r = HTTPS.request(Query, function (res) {
                onResponse(task.taskId, res)
            })
        } else {
            r = HTTP.request(Query, function (res) {
                onResponse(task.taskId, res)
            });
        }
        r.on("error", function () { onResponse(task.taskId) })
        r.write(contents, 'utf8');
        r.end();
        r.setTimeout(task.timeout, function () { onResponse(task.taskId) })
    }
    catch (e) {
        process.nextTick(function () { onResponse(task.taskId) });
    }
}